
import html
from collections import abc
import numbers
import functools
from functools import singledispatch
import time
from typing import ContextManager
global_var = 10


def package_close():
    """ that's ok, reference global variables, but no midifies
    """
    inner_var = 10
    print(inner_var)
    print(global_var)


def package_close_with_modify():
    """ run with error, because global variables was detected modification
    """
    inner_var = 10
    print(inner_var)
    print(global_var)
    # error, 未绑定错误
    global_var = 10


class Average():
    """constantly add element and calculate it's average value.
    """

    def __init__(self) -> None:
        self.series = []

    def __call__(self, new_value: int) -> float:
        self.series.append(new_value)
        total = sum(self.series)
        return total / len(self.series)


avg_ins = Average()
avg_ins(10)
avg_ins(5)
avg_ins(11)


def make_averager() -> function:
    """package close example, like the class above

    Returns:
        function: caculate the average value
    """
    series = []

    def averager(new_value: int) -> float:
        series.append(new_value)
        total = sum(series)
        return total / len(series)
    return averager


def make_averager1() -> function:
    """like above, but using mutable variable way

    Returns:
        function: like above
    """
    total = 0
    count = 0

    def averager(new_value: int) -> float:
        # unbound error
        count += 1
        total += new_value
        return count / total


def make_averager2() -> function:
    """like above right way

    Returns:
        function:
    """
    total = 0
    count = 0

    def averager(new_value: int) -> float:
        # mark variable to be free variable
        nonlocal count, total
        count += 1
        total += new_value
        return count / total


avg = make_averager()

avg(10)
avg(11)
avg(5)

# local variables
avg.__code__.co_varnames

# free variables
avg.__code__.co_freevars


def clock(func: function) -> function:
    """decorator demo

    Args:
        func (function): decoratored function

    Returns:
        function: actual performed function
    """
    def clocked(*args):
        """Calculate decoratored function perform time

        Returns:
            [type]: function return value
        """
        t0 = time.perf_counter()
        result = func(*args)
        elasped = time.perf_counter() - t0
        name = func.__name__
        arg_str = ', '.join(repr(arg) for arg in args)
        print('[%0.8fs] %s(%s) -> %r' % (elasped, name, arg_str, result))
        return result
    return clocked


@clock
def snooze(seconds: int):
    time.sleep(seconds)


# above equals
snooze = clock(snooze)


@clock
def factorial(n):
    return 1 if n < 2 else n * factorial(n - 1)


# support kewords parameters

def clock1(func: function) -> function:

    @functools.wraps(func)
    def clocked(*args, **kwargs):
        t0 = time.time()
        result = func(*args, **kwargs)
        elasped = time.time() - t0
        name = func.__name__
        arg_lst = []
        if args:
            arg_lst.append(', '.join(repr(arg) for arg in args))
        if kwargs:
            pairs = ['%s=%s' % (k, w) for k, w in sorted(kwargs.items)]
            arg_lst.append(', '.join(pairs))
        arg_str = ', '.join(arg_lst)
        print('[%0.8fs] %s(%s) -> %r' % (elasped, name, arg_str, result))
        return result
    return clocked


# use functools.lru_cache cache result
@functools.lru_cache()
@clock
def fibonacci(n):
    if n < 2:
        return n
    return fibonacci(n - 2) + fibonacci(n - 1)


# single dispatch function(单分派泛函数)
# when invoke htmlize function, different type trigger different operation method
# something like 重载 override
@singledispatch
def htmlize(obj):
    content = html.escape(repr(obj))
    return '<pre>{}</pre>'.format(content)


@htmlize.register(str)
def _(text):
    content = html.escape(text).replace('\n', '<br>\n')
    return '<pre>{0}</pre>'.format(content)


@htmlize.register(numbers.Integral)
def _(n):
    return '<pre>{0} (0x{0:x)</pre>'.format(n)


@htmlize.register(tuple)
@htmlize.register(abc.MutableSequence)
def _(seq):
    inner = '</li>\n<li>'.join(htmlize(item) for item in seq)
    return '<ul>\n<li>' + inner + '</li>\n</ul>'


# parameterize decorator(也就是装饰器可以接受参数，用来设置装饰器的行为)
def register(active=True) -> function:
    registry = []
    def decorator(func: function) -> function:
        """real decorator

        Args:
            func (function): 被装饰函数

        Returns:
            function: 返回原来的函数
        """
        print('running register(active=%s)->decorator(%s)' % (active, func))
        if active:
            registry.add(func)
        else:
            registry.discard(func)
        return func
    return decorator
